﻿using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HFUTCourseSimulation.Util;

namespace HFUTCourseSimulation.Kernel {

    ///// <summary>
    ///// 一个标识符，用于指代学期中指定周的指定星期的指定节次
    ///// </summary>
    //internal class ArrangerSpot : IEquatable<ArrangerSpot> {
    //    public ArrangerSpot(int week, int day, int index) {
    //        this.week = week;
    //        this.day = day;
    //        this.index = index;
    //    }

    //    /// <summary>
    //    /// 教学周
    //    /// </summary>
    //    public int week;
    //    /// <summary>
    //    /// 星期几
    //    /// </summary>
    //    public int day;
    //    /// <summary>
    //    /// 节次
    //    /// </summary>
    //    public int index;

    //    public override bool Equals(object obj) {
    //        return Equals(obj as ArrangerSpot);
    //    }

    //    public bool Equals(ArrangerSpot other) {
    //        return !(other is null) &&
    //               week == other.week &&
    //               day == other.day &&
    //               index == other.index;
    //    }

    //    public override int GetHashCode() {
    //        int hashCode = -206993699;
    //        hashCode = hashCode * -1521134295 + week.GetHashCode();
    //        hashCode = hashCode * -1521134295 + day.GetHashCode();
    //        hashCode = hashCode * -1521134295 + index.GetHashCode();
    //        return hashCode;
    //    }

    //    public static bool operator ==(ArrangerSpot left, ArrangerSpot right) {
    //        return EqualityComparer<ArrangerSpot>.Default.Equals(left, right);
    //    }

    //    public static bool operator !=(ArrangerSpot left, ArrangerSpot right) {
    //        return !(left == right);
    //    }
    //}

    /// <summary>
    /// 课程安排器。
    /// 负责将课程安排到每周之中，并提示用户错误等各种信息。
    /// </summary>
    public static class Arranger {

        /// <summary>
        /// 开始安排课程
        /// </summary>
        /// <returns>安排好的，用于呈现的课程。如果安排失败，则为null，查看汇报器来了解具体差错</returns>
        public static Data.Presentation.Semester Arrange(Data.Storage.Semester semester, Reporter reporter) {
            var builtSemester = CheckSemester(semester, reporter);
            if (builtSemester is null) return null;

            var arrangeMap = ArrangeCourses(builtSemester, reporter);
            var renderResult = ConcludeCourses(builtSemester, arrangeMap, reporter);

            return renderResult;
        }

        #region 验证用户课表输入

        /// <summary>
        /// 检查用户输入的学期数据是否正确
        /// </summary>
        /// <returns>如果检查无误则返回检查好的结构，否则返回null</returns>
        private static Data.Built.Semester CheckSemester(Data.Storage.Semester semester, Reporter reporter) {
            // 检查Semester
            reporter.Info($"正在检查学期设置...");
            if (semester.StartDate.DayOfWeek != DayOfWeek.Monday) {
                reporter.Error($"起始日期{semester.StartDate:yyyy-MM-dd}不是星期一");
                return null;
            }
            var rv = CheckInteger(reporter, semester.WeekCount, 1, 100, "周数", "0 < N");
            if (rv is null) return null;
            int weekCount = rv.Value;
            rv = CheckInteger(reporter, semester.IndexCount, 1, 100, "节次数", "0 < N");
            if (rv is null) return null;
            int indexCount = rv.Value;
            rv = CheckInteger(reporter, semester.BreakfastAt, 0, indexCount, "早餐时间点", "0 <= N <= 节次数");
            if (rv is null) return null;
            int breakfastAt = rv.Value;
            rv = CheckInteger(reporter, semester.LunchAt, breakfastAt, indexCount, "午餐时间点", "早餐时间点 <= N <= 节次数");
            if (rv is null) return null;
            int lunchAt = rv.Value;
            rv = CheckInteger(reporter, semester.DinnerAt, lunchAt, indexCount, "晚餐时间点", "午餐时间点 <= N <= 节次数");
            if (rv is null) return null;
            int dinnerAt = rv.Value;

            reporter.Info($"正在检查课程设置...");
            var courses = new List<Data.Built.Course>();
            foreach (var course in semester.Courses) {
                // 检查课程（实际上只有安排需要检查）
                reporter.Info($"正在检查课程：{course.Name}...");

                var schedules = new List<Data.Built.Schedule>();

                foreach (var schedule in course.Schedules) {
                    // 检查安排
                    var week = CheckIntegerCollection(reporter, schedule.Week, 1, weekCount, "周", "1 <= N <= 周数");
                    var day = CheckIntegerCollection(reporter, schedule.Day, 1, 7, "星期", "1 <= N <= 7");
                    var index = CheckIntegerCollection(reporter, schedule.Index, 1, indexCount, "节次", "1 <= N <= 节次数");

                    // 如果格式均正确，就添加这个安排
                    if (!(week is null || day is null || index is null)) {
                        schedules.Add(new Data.Built.Schedule() {
                            week = week,
                            day = day,
                            index = index
                        });
                    }
                }

                // OK，插入课程
                courses.Add(new Data.Built.Course() {
                    name = course.Name,
                    description = course.Description,
                    color = ColorTrans.ToStandardColorPair(course.Color),
                    schedules = schedules
                });
            }

            // OK无误，返回结果
            return new Data.Built.Semester() {
                startDate = semester.StartDate,
                weekCount = weekCount,
                indexCount = indexCount,
                breakfastAt = breakfastAt,
                lunchAt = lunchAt,
                dinnerAt = dinnerAt,
                courses = courses
            };
        }
        /// <summary>
        /// 检查整数的辅助函数。
        /// 检查范围时含提供的最小值min和最大值max。
        /// field_name是字段的名称，field_require是字段的要求，均用于用户提示。
        /// 返回null表示检查失败，否则返回检查好的值。
        /// </summary>
        private static int? CheckInteger(Reporter reporter, string s, int min, int max, string field_name, string field_require) {
            if (int.TryParse(s, out int rv)) {
                if (rv < min || rv > max) {
                    reporter.Error($"{field_name}的值“{s}”超出范围。要求：{field_require}");
                    return null;
                } else {
                    return rv;
                }
            } else {
                reporter.Error($"{field_name}的值“{s}”不是有效数字");
                return null;
            }
        }
        /// <summary>
        /// 整数集合的两种类型
        /// </summary>
        private enum IntegerCollectionKind {
            Individual,
            Range
        }
        /// <summary>
        /// 检查整数集合的辅助函数，即检查Schedule中的三个字段Week，Day和Index的格式。
        /// 各类参数含义与上面函数相同，只不过是作用到每一项上。
        /// </summary>
        private static IEnumerable<int> CheckIntegerCollection(Reporter reporter, string s, int min, int max, string field_name, string field_require) {
            // 两种整数集合所特有的字符常量，用于分割和分析
            const char COMMA = ',';
            const char DASH = '-';

            // 分析文本组成，决定其整数集合类型
            // 如果包含DASH，则肯定是Range，否则按Individual处理
            // （比如只有一项的Individual是没有COMMA的）
            IntegerCollectionKind kind;
            if (s.Contains(DASH)) {
                kind = IntegerCollectionKind.Range;
            } else {
                kind = IntegerCollectionKind.Individual;
            }

            // 根据整数集合类型，决定分割用字符
            char split_char;
            switch (kind) {
                case IntegerCollectionKind.Individual:
                    split_char = COMMA;
                    break;
                case IntegerCollectionKind.Range:
                    split_char = DASH;
                    break;
                default:
                    return null;
            }

            // 开始分割文本并将每一项转换为整数，同时检查其范围
            var ints = new List<int>();
            foreach (var part in s.Split(split_char).Select((i) => i.Trim())) {
                if (int.TryParse(part, out int rv)) {
                    if (rv < min || rv > max) {
                        reporter.Error($"{field_name}的值“{s}”包含的整数超出范围。要求：{field_require}");
                        return null;
                    } else {
                        ints.Add(rv);
                    }
                } else {
                    reporter.Error($"{field_name}的值“{s}”包含无效的整数");
                    return null;
                }
            }

            // 按整数集合类型进行收尾检查并返回
            switch (kind) {
                case IntegerCollectionKind.Individual:
                    return new Data.Built.IndividualInt(ints);
                case IntegerCollectionKind.Range:
                    if (ints.Count != 2) {
                        reporter.Error($"{field_name}的值“{s}”不是有效的整数范围表达式");
                        return null;
                    } else {
                        return new Data.Built.RangedInt(ints[0], ints[1]);
                    }
                default:
                    return null;
            }
        }

        #endregion

        #region 安排课程

        /// <summary>
        /// 安排课程
        /// </summary>
        /// <returns>返回安排好的课程表</returns>
        private static int[,,] ArrangeCourses(Data.Built.Semester semester, Reporter reporter) {
            // 创建课程表并预填充所有项为-1。
            var arrangeMap = new int[semester.weekCount, 7, semester.indexCount];
            for (var week = 0; week < semester.weekCount; ++week) {
                for (var day = 0; day < 7; ++day) {
                    for (var index = 0; index < semester.indexCount; ++index) {
                        arrangeMap[week, day, index] = -1;
                    }
                }
            }

            // 遍历所有课程安排开始排课
            // 排课冲突并不是严重错误，因此不需要强制退出
            for (int course_index = 0; course_index < semester.courses.Count; ++course_index) {
                var course = semester.courses[course_index];
                foreach (var schedule in course.schedules) {
                    foreach (var week in schedule.week) {
                        foreach (var day in schedule.day) {
                            foreach (var index in schedule.index) {
                                // 获取课程占用情况并分析
                                var occupied_course_index = arrangeMap[week - 1, day - 1, index - 1];
                                if (occupied_course_index < 0) {
                                    // 没有占用的课程，可以放心安排
                                    arrangeMap[week - 1, day - 1, index - 1] = course_index;
                                } else {
                                    // 发现课程占用，汇报占用的课程并记录到flag
                                    var occupied_course = semester.courses[occupied_course_index];
                                    reporter.Error($"课程冲突：无法将{course.name}安排到周{week}，星期{day}，第{index}节。因为此处已被{occupied_course.name}占据");
                                }
                            }
                        }
                    }
                }
            }

            // 返回结果
            return arrangeMap;
        }

        #endregion

        #region 总结课程为方便渲染的结构

        /// <summary>
        /// 将安排好的课程总结为方便渲染的结果
        /// </summary>
        /// <returns>总结好的可用于渲染的结果</returns>
        private static Data.Presentation.Semester ConcludeCourses(Data.Built.Semester semester, int[,,] arrangeMap, Reporter reporter) {
            var dateCursor = semester.startDate;

            var weeks = new List<Data.Presentation.Week>();
            for (var week = 1; week <= semester.weekCount; ++week) {
                var days = new List<Data.Presentation.Day>();
                for (var day = 1; day <= 7; ++day) {
                    // 获取日期的字符串形式，并自增天数
                    var dateString = dateCursor.ToString("MM/dd");
                    dateCursor = dateCursor.AddDays(1);

                    // 获取这一日的课程
                    var lessons = new List<Data.Presentation.Lesson>();
                    Data.Presentation.Lesson current_lesson = null;
                    int previous_course_index = -1;
                    for (var index = 1; index <= semester.indexCount; ++index) {
                        // 比较上一个课程和当前课程的区别
                        var this_course_index = arrangeMap[week - 1, day - 1, index - 1];
                        if (this_course_index != previous_course_index) {
                            // 课程发生了变化，我们需要根据这种变化做出操作。
                            // 如果有上一任课程，我们就把它push到列表中。
                            if (previous_course_index >= 0) {
                                lessons.Add(current_lesson);
                                current_lesson = null;
                            }
                            // 然后如果当前有课程，就设置当前课程
                            if (this_course_index >= 0) {
                                var this_course = semester.courses[this_course_index];
                                current_lesson = new Data.Presentation.Lesson() {
                                    name = this_course.name,
                                    description = this_course.description,
                                    color = this_course.color,
                                    startIndex = index,
                                    indexSpan = 1
                                };
                            }
                        } else {
                            // 课程未曾发生改变，如果当前有课程，我们只需要单纯地自增其节次跨度
                            if (previous_course_index >= 0) {
                                current_lesson.indexSpan++;
                            }
                        }
                        // 更新上一个课程的index
                        previous_course_index = this_course_index;
                    }
                    // 收尾处理最后一个课程
                    if (previous_course_index >= 0) {
                        lessons.Add(current_lesson);
                        current_lesson = null;
                    }

                    // 将这一日添加到这周中
                    days.Add(new Data.Presentation.Day() {
                        date = dateString,
                        lessons = lessons.ToImmutableList()
                    });
                }

                // 将这一星期添加教学周中
                weeks.Add(new Data.Presentation.Week() {
                    days = days.ToImmutableList()
                });
            }

            // 以教学周构建并返回学期
            return new Data.Presentation.Semester() {
                weekCount = semester.weekCount,
                indexCount = semester.indexCount,
                breakfastAt = semester.breakfastAt,
                lunchAt = semester.lunchAt,
                dinnerAt = semester.dinnerAt,
                weeks = weeks.ToImmutableList()
            };
        }

        #endregion

    }
}
